package com.landun.market.web.interceptor;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.landun.market.pojo.Constants;
import com.landun.market.pojo.enums.ResponseCode;
import com.landun.market.service.ISecurityService;
import com.landun.market.util.ConvertUtils;
import com.landun.market.util.HostUtils;
import com.landun.market.web.annotation.Authenticate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;


/**
 * @author jiang.chuan
 * @date 2018-05-12
 */
@Slf4j
public class AuthenticationInterceptor extends HandlerInterceptorAdapter implements HandlerInterceptor {
    private final ISecurityService securityService;
    private final static int CACHE_DURATION = 2;

    // 令牌缓存，the key is token, the value is it's status
    private static Cache<String, Boolean> tokenCache = Caffeine.newBuilder()
            .initialCapacity(5).maximumSize(1_000)
            .expireAfterWrite(CACHE_DURATION, TimeUnit.MINUTES)
            .build();

    AuthenticationInterceptor(ISecurityService securityService) {
        this.securityService = securityService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 如果是 Debug 模式也不拦截
        if (ConvertUtils.toBoolean(request.getParameter("debug"), false)) {
            log.info("对({})不需要登录", request.getRequestURI());
            return true;
        }

        // 不需要拦截 Authenticate.False
        Method handlerMethod = ((HandlerMethod) handler).getMethod();
        Class handlerClass = handlerMethod.getDeclaringClass();

        Authenticate authenticateAnnotation = handlerMethod.getAnnotation(Authenticate.class);
        if (null == authenticateAnnotation) {
            authenticateAnnotation = (Authenticate) handlerClass.getAnnotation(Authenticate.class);
        }

        // 如果没有配置必须登录，则直接放行
        if (null != authenticateAnnotation && !authenticateAnnotation.value()) {
            log.info("对({}#{})不需要登录", handlerClass.getName(), handlerMethod.getName());
            return true;
        }

        // 检查令牌是否有效
        if (isAuthenticated(request)) {
            return true;
        }

        // 未登陆
        if (InterceptorUtils.isAjaxRequest(request, handlerClass, handlerMethod)) {
            InterceptorUtils.ajaxReturn(response, ResponseCode.UNAUTHORIZED);
        }
        return false;
    }


    /**
     * 通过远程调用的方式来验证用户是否拥有权限
     *
     * @param httpRequest HttpServletRequest
     * @return true: 验证通过，否则不通过
     */
    private boolean isAuthenticated(HttpServletRequest httpRequest) {
        String token = StringUtils.trimToEmpty(httpRequest.getHeader(Constants.TOKEN_PARA_NAME));
        if (StringUtils.isBlank(token)) {
            return false;
        }

        // 先验证 授权令牌
        Boolean authenticated = tokenCache.getIfPresent(token);
        if (null != authenticated && authenticated) {
            log.info("Got authenticated status:({}) from local cache for token:({})", authenticated, token);
            return authenticated;
        }

        authenticated = securityService.isAuthenticated(token, HostUtils.getRequestIp(httpRequest));
        tokenCache.put(token, authenticated);

        // 放在请求对象中，方便后续API直接使用
        httpRequest.setAttribute(token, authenticated);
        return authenticated;
    }
}
